//
//  MainViewController.swift
//  SearchHeader
//
//  Created by 万孟超 on 2023/10/11.
//

import Cocoa

class FileCheckVC: NSViewController {
    @IBOutlet weak var projectPath: NSTextField!
    
    @IBOutlet var logInfoView: NSTextView!
    @IBOutlet var searchResultFileTextView: NSTextView!
    @IBOutlet weak var podsListTableView: NSTableView!
    
    @IBOutlet weak var logScrollView: NSScrollView!
    @IBOutlet weak var logView: NSView!
    @IBOutlet weak var leftOperationView: NSView!
    /// Pods文件夹的路径
    var podsFilePath: String = "" {
        didSet {
            Async(.global()) { [self] in
                let podsFileResultTuple = File.obtainPodsFileMap(podsFilePath)
                podsListDataSource = podsFileResultTuple.0
                podsFileDictionary = podsFileResultTuple.1
                Async(.main) { [self] in
                    podsListTableView.reloadData()
                }
            }
        }
    }
    
    var logString: String = "" {
        didSet {
            Async(.main) { [self] in
                logInfoView.textStorage?.setAttributedString(logString.attributedString)
                logInfoView.scrollLineDown(nil)
            }
        }
    }
    
    /// .xcodeProj的路径
    var projectNamePath: String = "" {
        didSet {
            Async(.global()) { [self] in
                logString += "\n" + "正在获取项目中导入的文件"
                projectUsedClassFile = File.findReferencedFiles(projectPath: projectNamePath)
                logString += "\n" + "项目中导入的文件获取成功"
            }
        }
    }
    
    /// 工程路径
    var searchFloderPath: String = ""
    /// Pods忽略的framework数组
    var podsIgnoreLastFloderArray: [String] = []
    /// 头文件导入不规范字典 framework的名称为key，不规范头文件数组为value
    var filesDictionary: [String: [String]] = [:]
    
    var fileSearchResult = SearchResult(results: [])
    /// 获取到的Pods列表
    var podsListDataSource: [String] = []
    /// 工程中使用到的OC文件
    var projectUsedClassFile: [String] = []
    /// 搜索时，文件目录忽略列表
    let searchIgnoreFloderArray = [".xcframework", ".framework", ".xcassets", ".lproj", "Pods"]
    
    @IBOutlet weak var resultMapScroll: NSScrollView!
    @IBOutlet weak var podsMapScroll: NSScrollView!
    /// 存储每个文件所在的位置
    /// 第一个是文件，第二个是所在的文件夹
    /// 例如："AFNetworking.h" : "AFNetworking"
    var podsFileDictionary: [String: String] = [:]
    
    var isDevelopmentProject: Bool = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        NotificationCenter.default.addObserver(self, selector: #selector(replaceFile(_:)), name: replaceFileNotification, object: nil)
        // Do any additional setup after loading the view.
        
        resultMapScroll.wantsLayer = true
        resultMapScroll.layer?.cornerRadius = 15
        
        podsMapScroll.wantsLayer = true
        podsMapScroll.layer?.cornerRadius = 15
        
        leftOperationView.wantsLayer = true
        leftOperationView.layer?.cornerRadius = 15
        leftOperationView.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.3).cgColor
        
        logView.wantsLayer = true
        logView.layer?.cornerRadius = 15
        logView.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.3).cgColor
        
    }
    
    override var representedObject: Any? {
        didSet {
            // Update the view, if already loaded.
        }
    }
    @IBAction func developmentButtonChanged(_ sender: NSSwitch) {
        if sender.state == .on {
            isDevelopmentProject = true
        }else{
            isDevelopmentProject = false
        }
        if !searchFloderPath.isEmpty {
            obtainPaths()
        }
    }
    
    @IBAction func chooseProjectPathAction(_ sender: NSButton) {
        File.finder(singleSelectBlock: {[weak self] path in
            self?.podsListDataSource.removeAll()
            self?.podsIgnoreLastFloderArray.removeAll()
            self?.podsListTableView.reloadData()
            self?.searchFloderPath = path
            self?.projectPath.stringValue = path
            self?.searchResultFileTextView.string = ""
            self?.obtainPaths()
        })
    }
    
    @IBAction func searchNotStandardHeader(_ sender: NSButton) {
        Async(.global()) { [self] in
            filesDictionary.removeAll()
            fileSearchResult = SearchResult(results: [])
            if projectUsedClassFile.isEmpty || podsFileDictionary.isEmpty {
                return
            }
            var isDirectory: ObjCBool = false
            if FileManager.default.fileExists(atPath: searchFloderPath, isDirectory: &isDirectory), isDirectory.boolValue {
                let enumerator = FileManager.default.enumerator(atPath: searchFloderPath)
                while let file = enumerator?.nextObject() as? String {
                    
                    let filePath = searchFloderPath + "/" + file
                    var isDirectory: ObjCBool = false
                    if FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory), isDirectory.boolValue {
                        if file.contains(searchIgnoreFloderArray) {
                            enumerator?.skipDescendants()
                        }
                        continue
                    }
                    dealFileContent(filePath, file: file)
                }
            }else{
                let file = searchFloderPath.components(separatedBy: "/").last!
                dealFileContent(searchFloderPath, file: file)
            }
            Async(.main) { [self] in
                searchResultFileTextView.textStorage?.setAttributedString(filesDictionary.jsonValue.attributedString)
                logString += "\n" + "🔚文件搜索完成"
                logInfoView.scrollToEndOfDocument(nil)
            }
            
        }
    }
    
    @IBAction func searchResultExport(_ sender: NSButton) {
        
        File.exportFile(filesDictionary.jsonValue)
    }
    
    @IBAction func showHelpAction(_ sender: NSButton) {
        let alertData = ["注意！！！：工具只是辅助检查，查找出的结果可能出错！！！",
                         "！！！不要盲目使用替换功能，根据实际情况判断可否使用快速替换！！！",
                         "！！！一键替换使用前，一定要确保可以替换！！！",
                         "！！！确保替换之后可以恢复，避免翻车！！！",
                         "---------------------------------查询原理------------------------------",
                         "1️⃣：根据选择的路径，获取Pods文件夹路径",
                         "2️⃣：遍历Pods文件夹中的文件，生成.h文件和framewrok名称的映射{\".h\":\"framework\"}",
                         "3️⃣：根据xcodeproj中project.proj文件，解析出当前项目导入的文件",
                         "4️⃣：遍历当前导入的文件，获取文件每一行的内容",
                         "5️⃣：正则表达式匹配#import \"xxx.h\"和#import <xxx.h>",
                         "6️⃣：查询xxx.h文件是否在Pods的映射的所有key中，如果存在，则当前头文件引用不规范",
                         "---------------------------------使用说明------------------------------",
                         "1️⃣.如果当前项目是以路径导入方式创建的项目，先将Development Pods打开",
                         "2️⃣.点击Brower，选择项目的路径（clone代码时创建的文件夹）",
                         "3️⃣.选择后右边窗口会给出进度日志，等待提示`项目中导入的文件获取成功`",
                         "4️⃣.底部左边视图会展示当前项目所有的Pods文件，勾选Cell时，会忽略选择的framework",
                         "5️⃣.点击Search按钮，等待日志提示文件搜索完成",
                         "6️⃣.查找完成后，底部右边窗口会给出查询结果，key为framewrok，value为头文件数组，意思为这几个头文件引用不规范，这个头文件属于framework这个库",
                         "7️⃣.点击Export可将key-value导出",
                         "8️⃣.点击Excel通过Excel视图查看详细情况，包括哪个头文件引用不规范，所在的framework，在哪个文件中不规范，在第几行，不规范的内容，建议的修改格式",
                         "9️⃣.点击头部的全局替换可以一键修改所有的头文件(慎用，我都不敢，点击了可以在SourceTree中检查)",
                         "🔟.点击一键替换可以替换单个文件的内容"]
        NSPopover.showPopover(alertData, of: sender)
    }
    
    @IBAction func showInExcel(_ sender: NSButton) {
        let excelWindow = ExcelWindowC(viewController: ExcelVC(data: fileSearchResult))
        excelWindow.showWindow(self)
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

extension FileCheckVC {
    
    func obtainPaths() {
        logString += "\n" + "正在获取Pods信息，稍等一下"
        podsFilePath = File.obtainPodsFilePath(searchFloderPath)
        logString += "\n" + "Pods库列表获取成功"
        logString += "\n" + "正在获取xcodeproj文件"
        projectNamePath = File.obtainXcodeProjFilePath(searchFloderPath, isDevelopmentProject: isDevelopmentProject)
        logString += "\n" + "xcodeproj文件获取成功"
    }
    
    @objc func replaceFile(_ notifi: Notification) {
        if let clickTag = notifi.userInfo?["tag"] as? Int {
            let first = clickTag / 100 - 1
            let second = clickTag % 100 / 10 - 1
            let third = clickTag % 100 % 10 - 1
            if let replaceModel = fileSearchResult[first]?[second]?[third] {
                File.replaceFileContent(replaceModel.filePath, line: replaceModel.line, replace: replaceModel.rightImportContent)
            }
        }else if let message = notifi.userInfo?["tag"] as? String {
            if message == "replaceAll" {
                for result in fileSearchResult.results {
                    for list in result.fileList {
                        for replaceModel in list.errorImportArray {
                            File.replaceFileContent(replaceModel.filePath, line: replaceModel.line, replace: replaceModel.rightImportContent, alert: false)
                        }
                    }
                }
                Alert.showAlert(message: "替换完成", sureTitle: "确认")
            }
        }
    }
    
    func searchFile(withName fileName: String, in folderPath: String) -> String? {
        let fileManager = FileManager.default
        let subfolders = try? fileManager.contentsOfDirectory(atPath: folderPath)
        
        for subfolder in subfolders ?? [] {
            let subfolderPath = folderPath + "/" + subfolder
            
            var isDirectory: ObjCBool = false
            if fileManager.fileExists(atPath: subfolderPath, isDirectory: &isDirectory), isDirectory.boolValue {
                if let filePath = searchFile(withName: fileName, in: subfolderPath) {
                    return filePath
                }
            } else if subfolder == fileName {
                return folderPath.components(separatedBy: "/").last!
            }
        }
        
        return nil
    }
    
    func dealFileContent(_ filePath: String, file: String) {
        if file.hasSuffix(".h") || file.hasSuffix(".m") || file.hasSuffix(".mm") || file.hasSuffix(".pch"){
            if projectUsedClassFile.contains(filePath) {
                if let content = try? String(contentsOfFile: filePath, encoding: .utf8) {
                    let lines = content.components(separatedBy: .newlines)
                    for (idx, line) in lines.enumerated() {
                        if line.hasPrefix("@interface") ||
                            line.hasPrefix("@implementation") ||
                            line.starts(with: "//") {
                            continue
                        }
                        let pattern = #"#import "[^"]+\.h""#
                        
                        if let _ = line.range(of: pattern, options: .regularExpression) {
                            let components = line.components(separatedBy: " ")
                            if let fileName = components.last?.replacingOccurrences(of: "\"", with: "") {
                                delectionFile(fileName, filePath: filePath, idx: idx, line: line)
                            }
                        }
                        
                        /// 只检测 #import <aaa.h>形式
                        let patternOther = #"#import <[^"]+\.h>"#
                        if let _ = line.range(of: patternOther, options: .regularExpression) {
                            let components = line.components(separatedBy: " ")
                            let lastComponent = components.last
                            if let fileName = lastComponent?.replacingOccurrences(of: "<", with: "").replacingOccurrences(of: ">", with: "") {
                                if !fileName.contains("/") {
                                    delectionFile(fileName, filePath: filePath, idx: idx, line: line)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    func delectionFile(_ fileName: String, filePath: String, idx: Int, line: String) {
//        logString += "\n" + "🔍" + fileName
        if podsFileDictionary.keys.contains(fileName) {
            if let frameworkName = podsFileDictionary[fileName] {
                if !podsIgnoreLastFloderArray.contains(frameworkName) {
                    if !fileSearchResult.contains(frameworkName) {
                        fileSearchResult.results.append(FileResult(frameworkName: frameworkName, fileList: []))
                    }
                    let frameworkModel = fileSearchResult[frameworkName]!
                    if !frameworkModel.contains(fileName) {
                        frameworkModel.fileList.append(FileNameModel(fileName: fileName, errorImportArray: []))
                    }
                    let fileModel = frameworkModel[fileName]!
                    /// 当前错误头文件导入所在的文件信息
                    let errorFileModel = ErrorInfo(fileName: filePath.components(separatedBy: "/").last!, filePath: filePath, line: idx + 1, importContent: line, rightImportContent: "#import <\(frameworkName)/\(fileName)>")
                    fileModel.errorImportArray.append(errorFileModel)
                    
                    if filesDictionary[frameworkName] == nil {
                        filesDictionary[frameworkName] = []
                    }
                    if !filesDictionary[frameworkName]!.contains(fileName) {
                        filesDictionary[frameworkName]!.append(fileName)
                        
                    }
                }
            }
        }
    }
}

extension FileCheckVC: NSTableViewDelegate, NSTableViewDataSource {
    func numberOfRows(in tableView: NSTableView) -> Int {
        podsListDataSource.count
    }
    
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let rowView = CellView()
        let frameworkName = podsListDataSource[row]
        rowView.titleLabel.stringValue = frameworkName
        rowView.isSelect = podsIgnoreLastFloderArray.contains(frameworkName)
        rowView.checkAction = {[weak self] isSelect in
            if isSelect {
                self?.podsIgnoreLastFloderArray.appendElement(frameworkName)
            }else{
                self?.podsIgnoreLastFloderArray.removeObject(frameworkName)
            }
        }
        return rowView
    }

}
